# -*- coding: utf-8 -*-
"""细化方法中参数的推理，用于domain.py的调用"""

__author__ = 'Yu XH'

import math
from myAI.hexmap.hex import *
from myAI.attdamage.handle_shoot import *
from myAI.const import *
from myAI.hexmap.handle_map import *

CM_HUNT_PERIOD = 100


def is_moving(obj):
    """ 2020-4-24
    是否正在机动的判断
    """
    if obj['speed'] > 0:
        return True
    else:
        return False


def has_stopped(obj):
    """ 2020-4-25
    是否已经停止的判断
    """
    if obj['stop'] == 1:
        return True
    else:
        return False


def get_mod(obj_id, myAgent):
    """ 2020-5-19
    确定算子机动的类型，gen_move_route函数中的参数
    """
    obj = myAgent.get_bop(obj_id)
    if obj['type'] == BopType.Infantry:
        return MoveType.Walk
    elif obj['type'] == BopType.Vehicle:
        return MoveType.Maneuver
    elif obj['type'] == BopType.Aircraft:
        return MoveType.Fly
    else:
        raise Exception('不能确定机动类型')


def guide_ability(obj_id, myAgent):
    """ 2020-4-15
    确定算子是否有引导射击能力
    """
    opr = myAgent.get_bop(obj_id)
    if (opr['guide_ability'] == 1):  # 如果有引导射击的属性
        if opr['launcher']:  # 如果有“所属车辆”
            if 73 in myAgent.get_bop(opr['launcher'])['carry_weapon_ids'].values():  # 如果所属车辆配有“重型导弹73”
                if myAgent.get_bop(opr['launcher'])['remain_bullet_nums'][100] > 0:  # 如果所属车辆还有剩余的重型导弹的弹药
                    return True
    return False


def guided_ability(obj_id, myAgent):
    """ 2020-4-20
    确定算子是否有被引导射击能力，检查对象是战车
    """
    if myAgent.get_bop(obj_id)['remain_bullet_nums'][100] <= 0:  # 如果所属车辆耗尽重型导弹的弹药
        return False
    for opr in myAgent.observation['operators']:  # 找出任意一个我方能引导当前算子的
        if opr['color'] == myAgent.color:
            if (opr['guide_ability'] == 1):  # 如果有引导射击的属性
                if opr['launcher'] == obj_id:  # 如果有“所属车辆”
                    return True
    return False


def shoot_ability(obj_id, myAgent):
    """ 2020-4-15
    确定算子opr是否有射击能力
    """
    opr = myAgent.get_bop(obj_id)
    if max(opr['remain_bullet_nums'].values()) > 0 or guide_ability(opr, myAgent):  # 如果有剩余弹药，或有引导射击能力
        return True
    else:
        return False


def move_n_shoot_ability(opr):
    """ 2020-5-8
    检查算子是否具有“行进间射击”能力
    """
    if opr['sub_type'] == 0 and opr['remain_bullet_nums'][0] > 0:  # 必须是坦克，同时有能力射击
        return True
    else:
        return False


def in_enemies_range(obj_id, myAgent):
    """ 2020-5-14
    判断算子obj当前是否在敌人的火力范围内
    """
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    obj_hex = obj['cur_hex']
    enemy_ids = obj['see_enemy_bop_ids']
    if not enemy_ids:
        return False
    for ene_id in enemy_ids:
        enemy = myAgent.get_bop(ene_id)
        distance = cal_distance(obj_hex, enemy['cur_hex'])
        weapons_id = enemy['carry_weapon_ids']
        for wp_id in weapons_id:
            dist_wp = get_weapon(wp_id).shoot_range[obj_type]
            if distance < dist_wp:
                return True
    return False


def confirm_enemies(myAgent, radius):
    """ 2020-4-30
    找出所有敌方算子，误差范围在radius距离以内
    """
    enemy_ids = []
    for enemy_info in myAgent.situations.enemies:
        enemy_id = enemy_info['obj_id']
        speed = enemy_info['operator']['basic_speed'] / 3.6
        final_step = enemy_info['step']
        cur_step = myAgent.observation['time']['cur_step']
        if (cur_step - final_step) * speed / 200 < radius + 1:  # 当radius==0时，只要行进没有达到200m，表示位于本格
            enemy_ids.append(enemy_id)
    return enemy_ids


def predict_enemy_locs(myAgent, enemy_info):
    """ 2020-5-26
    预测敌方算子（由myAgent.factionAI.enemies获取）的各点位上的概率
    :return: n_hex长度的向量，表示位于向量索引对应六角格的概率
    """
    discover_step = enemy_info['step']
    discover_loc = enemy_info['operator']['cur_hex']
    discover_loc_ind = mapid_2_matind(discover_loc, myAgent.get_map_size())
    ene_type = enemy_info['operator']['type']
    unsee_period = myAgent.observation['time']['cur_step'] - discover_step
    if ene_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
        loc_vec = (cost_matrix[discover_loc_ind] <= unsee_period) + 0  # 0-1向量
    elif ene_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        loc_vec = (cost_matrix[discover_loc_ind] <= unsee_period) + 0  # 0-1向量
    elif ene_type == BopType.Aircraft:
        dist_matrix = myAgent.my_map['dist_matrix']
        ene_speed = enemy_info['operator']['basic_speed'] / 3.6
        radius = unsee_period * ene_speed / 200.
        loc_vec = (dist_matrix[discover_loc_ind] <= radius) + 0  # 0-1向量
    else:
        raise Exception('目标算子类型输入错误！')
    sum_loc_vec = np.sum(loc_vec)
    sum_loc_vec = sum_loc_vec * 1. if sum_loc_vec > 0 else 1.
    loc_prob_vec = loc_vec / sum_loc_vec
    return loc_prob_vec


def get_rival_weapons(myAgent, obj_type):
    """ 2020-5-24
    输出敌方的所有可用武器
    """
    weapon_ids = []
    for enemy_info in myAgent.situations.enemies:
        carry_weapon_ids = enemy_info['operator']['carry_weapon_ids']
        remain_bullet_nums = enemy_info['operator']['remain_bullet_nums']
        for wp_id in carry_weapon_ids:
            weapon = get_weapon(wp_id)
            if remain_bullet_nums[weapon.type] > 0 and weapon.shoot_range[obj_type] > 0:
                weapon_ids.append(wp_id)
    return list(set(weapon_ids))


def shoot_check(obj_id, observation):
    """ 2020-4-19
    确定算子opr当前状态下是否可射击
    """
    if obj_id not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][obj_id]
    if ActionType.Shoot in opr_v_a.keys() or ActionType.GuideShoot in opr_v_a.keys():  # 如果算子opr当前可以执行射击行动/引导射击
        return True
    else:
        return False


def shoot_target_check(obj_id, tar_id, observation):
    """ 2020-4-24
    确定算子opr当前状态下是否可射击目标算子tar
    """
    if obj_id not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][obj_id]
    if 2 in opr_v_a.keys():
        if tar_id in [x['target_obj_id'] for x in opr_v_a[2]]:
            return True
    if 9 in opr_v_a.keys():
        if tar_id in [x['target_obj_id'] for x in opr_v_a[9]]:
            return True
    return False


def occupy_check(opr, observation):
    """ 2020-4-22
    确定算子opr当前是否可以执行占领动作
    """
    if opr['obj_id'] not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][opr['obj_id']]
    if ActionType.Occupy in opr_v_a.keys():  # 如果当前可夺控
        return True
    else:
        return False


def jm_shoot_check(opr, observation):
    """2020-7-1
    确定算子opr当前是否可以执行间瞄射击动作
    """
    if opr['obj_id'] not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][opr['obj_id']]
    if ActionType.JMPlan in opr_v_a.keys():
        return True
    else:
        return False


def get_on_check(ifv_id, passenger_id, observation):
    """ 2020-6-18
        确定当前passenger是否可以上车
        """
    if passenger_id in observation['valid_actions'].keys():
        if ActionType.GetOn in observation['valid_actions'][passenger_id].keys():
            if ifv_id in [action['target_obj_id'] for action in
                          observation['valid_actions'][passenger_id][ActionType.GetOn]]:
                return True
    return False


def get_off_check(ifv_id, passenger_id, observation):
    """ 2020-4-18
    确定当前passenger是否可以从ifv下车
    """
    if ifv_id in observation['valid_actions'].keys():
        if ActionType.GetOff in observation['valid_actions'][ifv_id].keys():
            if passenger_id in [tar['target_obj_id'] for tar in observation['valid_actions'][ifv_id][4]]:
                return True
    return False


def cm_getoff_check(obj_id, myAgent):
    """ 2020-4-26
    确定当前巡飞弹是否可以下车
    """
    # 下车条件：找出巡飞弹的装载车辆veh，如果当前天上没有veh发射的其它巡飞弹，即可下车
    veh_id = myAgent.get_bop(obj_id)['car']
    for opr in myAgent.observation['operators']:
        if opr['sub_type'] == 7 and opr['launcher'] == veh_id:
            return False
    return True


def change_state_check(obj_id, tar_state, observation):
    """ 2020-4-19
    确定算子当前是否可以切换到tar_state的状态
    """
    if obj_id in observation['valid_actions'].keys():
        if ActionType.ChangeState in observation['valid_actions'][obj_id].keys():
            if tar_state in [ts['target_state'] for ts in observation['valid_actions'][obj_id][6]]:
                return True
    return False


def cm_shoot_check(obj_id, myAgent):
    """ 2020-5-19
    巡飞弹是否实施射击的判断，输出射击flag和选中的射击目标ID
    """
    if (myAgent.get_bop(obj_id)['alive_remain_time'] <= CM_HUNT_PERIOD) or \
            (myAgent.observation['time']['cur_step'] >= 1800 - CM_HUNT_PERIOD):
        dist = int(myAgent.get_bop(obj_id)['alive_remain_time'] * 100 / 3.6 / 200) - 2  # 确定打击半径
        enemy_ids = confirm_enemies(myAgent, 2)
        if enemy_ids:
            candidates = []
            min_dist = 1000
            for id in enemy_ids:
                enemy = myAgent.get_enemy_info(id)['operator']
                dist_to_enemy = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                if dist_to_enemy <= dist:
                    candidates.append(id)
                elif dist_to_enemy < min_dist:
                    min_dist = dist_to_enemy
                    closest_enemy_outside = id
            if not candidates:  # 选择最近的进行打击
                return True, closest_enemy_outside
            # 为敌方算子分等级：压制的步兵、重型装甲车辆、未压制的步兵、复合装甲车辆
            candidates_type1 = []  # 存储所有压制的步兵
            candidates_type2 = []  # 存储所有重型装甲车辆
            candidates_type3 = []  # 存储所有未压制的步兵
            candidates_type4 = []  # 存储所有复合装甲车辆
            candidates_type5 = []  # 存储所有其它敌方单位
            for e_id in candidates:
                enemy_info = myAgent.get_enemy_info(e_id)
                enemy = enemy_info['operator']
                if enemy['type'] == BopType.Infantry:  # 步兵
                    if enemy['keep'] == 1:
                        distance = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                        elapse = myAgent.observation['time']['cur_step'] - enemy_info['step']
                        if enemy['keep_remain_time'] - elapse > distance * 7.2:
                            candidates_type1.append(enemy)
                        else:
                            candidates_type3.append(enemy)
                    else:
                        candidates_type3.append(enemy)
                else:  # 车辆
                    if enemy['armor'] == ArmorType.Heavy:
                        candidates_type2.append(enemy)
                    elif enemy['armor'] == ArmorType.Composit:
                        candidates_type4.append(enemy)
                    else:
                        candidates_type5.append(enemy)
            # 根据顺序确定打击对象，同类算子依据距离优先
            if candidates_type1:
                tar = min(candidates_type1,
                          key=lambda x:
                          cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                          (myAgent.observation['time']['cur_step'] -
                           myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )  # 根据距离上限选择，距离上限是指可能距我最远的
                return True, tar['obj_id']
            if candidates_type2:
                tar = min(candidates_type2, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type3:
                tar = min(candidates_type3, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type4:
                tar = min(candidates_type4, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.situations.getEnemyInfoByID(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type5:
                tar = min(candidates_type5, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
        else:  # 找不到敌方算子
            return False, None
    elif myAgent.get_bop(obj_id)['alive_remain_time'] >= 600:
        # 判断当前巡飞弹是所在战车发射的第1个
        first_flag = False
        car_id = myAgent.get_bop(obj_id)['launcher']
        for passenger_id in myAgent.get_bop(car_id)['passenger_ids']:
            if myAgent.get_bop(passenger_id)['sub_type'] == BopType.CruiseMissile:
                first_flag = True
        if first_flag:  # 如果是第1个
            enemy_ids = myAgent.get_bop(obj_id)['see_enemy_bop_ids']
            for id in enemy_ids:
                if cal_distance(myAgent.get_bop(obj_id)['cur_hex'], myAgent.get_bop(id)['cur_hex']) > 2:
                    enemy_ids.remove(id)
                if myAgent.get_bop(id)['type'] == BopType.Infantry:  # 步兵
                    if myAgent.get_bop(obj_id)['cur_hex'] == myAgent.get_bop(id)['cur_hex']:
                        enemy_ids.remove(id)
            if enemy_ids:
                for id in enemy_ids:
                    curr_terrain = get_terrain_info(myAgent.map.get_map_data(), myAgent.get_bop(id)['cur_hex'])
                    if myAgent.get_bop(id)['type'] == BopType.Infantry \
                            and myAgent.get_bop(id)['keep'] == 1 \
                            and curr_terrain != TerrainType.Forest:  # 步兵、压制、非从林地
                        return True, id
                    if myAgent.get_bop(id)['type'] == BopType.Vehicle \
                            and (myAgent.get_bop(id)['move_state'] == MoveState.March or
                                 myAgent.get_bop(id)['stack'] == 1) \
                            and curr_terrain == TerrainType.OpenLand:
                        return True, id
                return False, None
            else:
                return False, None
    return False, None


def cm_shoot_check_2nd(obj_id, myAgent):
    """ 2020-7-6 用于群队
    巡飞弹是否实施射击的判断，输出射击flag和选中的射击目标ID
    """
    # === 计算当前在空中的巡飞弹数量 ===
    cm_count = 0
    for opr in myAgent.situations.our_oprs:
        if opr['sub_type'] == BopType.CruiseMissile and opr['on_board'] == 0:
            cm_count += 1
    # === 最后生命的一击 ===
    if (myAgent.get_bop(obj_id)['alive_remain_time'] <= CM_HUNT_PERIOD) or \
            (myAgent.observation['time']['cur_step'] >= 3000 - CM_HUNT_PERIOD):
        dist = int(myAgent.get_bop(obj_id)['alive_remain_time'] * 100 / 3.6 / 200) - 2  # 确定打击半径
        enemy_ids = confirm_enemies(myAgent, 2)
        if enemy_ids:
            candidates = []
            min_dist = 1000
            for id in enemy_ids:
                enemy = myAgent.get_enemy_info(id)['operator']
                dist_to_enemy = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                if dist_to_enemy <= dist:
                    candidates.append(id)
                elif dist_to_enemy < min_dist:
                    min_dist = dist_to_enemy
                    closest_enemy_outside = id
            if not candidates:  # 选择最近的进行打击
                return True, closest_enemy_outside
            # 为敌方算子分等级：压制的步兵、无有利修正步兵、重型装甲车辆、复合装甲车辆
            candidates_type1 = []  # 存储所有压制的步兵
            candidates_type2 = []  # 存储无有利修正步兵
            candidates_type3 = []  # 存储其它步兵
            candidates_type4 = []  # 存储战车
            candidates_type5 = []  # 存储坦克
            for e_id in candidates:
                enemy_info = myAgent.get_enemy_info(e_id)
                enemy = enemy_info['operator']
                if enemy['type'] == BopType.Infantry:  # 步兵
                    keep_flag = False
                    if enemy['keep'] == 1:
                        distance = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                        elapse = myAgent.observation['time']['cur_step'] - enemy_info['step']
                        if enemy['keep_remain_time'] - elapse > distance * 7.2:
                            candidates_type1.append(enemy)
                            keep_flag = True
                    if not keep_flag:
                        if enemy['move_state'] == MoveState.Shelter:
                            candidates_type3.append(enemy)
                        elif enemy['stop'] != 1:
                            candidates_type3.append(enemy)
                        elif get_terrain_info(myAgent.map.get_map_data(), enemy['cur_hex']) in [TerrainType.Forest,
                                                                                                TerrainType.Resident]:
                            candidates_type3.append(enemy)
                        else:
                            candidates_type2.append(enemy)
                else:  # 车辆
                    if enemy['type'] == BopType.Vehicle:
                        candidates_type4.append(enemy)
                    elif enemy['type'] == BopType.Tank:
                        candidates_type5.append(enemy)
            # 根据顺序确定打击对象，同类算子依据距离优先
            if candidates_type1:
                tar = min(candidates_type1,
                          key=lambda x:
                          cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                          (myAgent.observation['time']['cur_step'] -
                           myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )  # 根据距离上限选择，距离上限是指可能距我最远的
                return True, tar['obj_id']
            if candidates_type2:
                tar = min(candidates_type2, key=lambda x:
                        cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                        (myAgent.observation['time']['cur_step'] -
                         myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type3:
                tar = min(candidates_type3, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type4:
                tar = min(candidates_type4, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.situations.getEnemyInfoByID(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type5:
                tar = min(candidates_type5, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
        else:  # 找不到敌方算子
            return False, None
    # === 把握机会的一击 ===
    elif myAgent.get_bop(obj_id)['alive_remain_time'] >= 600 and cm_count > 1:
        dist_threshold = 10
        cm = myAgent.get_bop(obj_id)
        cm_loc = cm['cur_hex']
        candidates_level_1, candidates_level_2 = [], []  # 无有利修正的步兵，只有一种有利修正的步兵
        for enemy_info in myAgent.situations.enemies:
            enemy = enemy_info['operator']
            if enemy['type'] == BopType.Infantry and enemy['keep'] == 0:  # 压制的步兵交给炮兵
                if 0 < cal_distance(cm_loc, enemy['cur_hex']) <= dist_threshold:
                    curr_terrain = get_terrain_info(myAgent.map.get_map_data(), enemy['cur_hex'])
                    rect_vec = [0] * 3
                    if curr_terrain in [TerrainType.Forest, TerrainType.Resident]:
                        rect_vec[0] = 1
                    if enemy['stop'] == 0:
                        rect_vec[1] = 1
                    if enemy['move_state'] == MoveState.Shelter:
                        rect_vec[2] = 1
                    if sum(rect_vec) <= 0:
                        candidates_level_1.append(enemy)
                    elif sum(rect_vec) <= 1:
                        candidates_level_2.append(enemy)
        if candidates_level_1:
            tar = min(candidates_level_1, key=lambda x: cal_distance(cm_loc, x['cur_hex']))
        elif candidates_level_2:
            tar = min(candidates_level_2, key=lambda x: cal_distance(cm_loc, x['cur_hex']))
        else:
            tar = None
        if tar:
            return True, tar['obj_id']
    return False, None


def rapid_march_check(obj, move_loc):
    """ 2020-5-1
    是否进行快速机动的判断
    > 后期可以加入任务完成紧迫性的判断
    """
    if obj['type'] == BopType.Aircraft:  # 空中算子
        return True
    if obj['sub_type'] == BopType.Artillery:  # 炮兵
        return True
    if obj['type'] == BopType.Vehicle:  # 车辆
        if cal_distance(obj['cur_hex'], move_loc) <= 3:  # 短距离机动
            return True
        else:
            return False
    if obj['type'] == BopType.Infantry:  # 人员
        return True


def force_stop_check(obj_id, move_loc, myAgent):
    """ 2020-5-9
    是否进行机动停止的判断
    """
    obj = myAgent.get_bop(obj_id)
    if obj['type'] == 3:  # 空中算子
        return True
    if not obj['move_path']:
        return False
    end_loc = obj['move_path'][-1]
    if move_loc == end_loc:
        return False
    cur_loc = obj['cur_hex']  # 需要完成当前的机动
    mod = 2 if obj['type'] == 1 else 0  # 机动方式，不考虑行军
    new_path = myAgent.map.gen_move_route(cur_loc, move_loc, mod)
    new_path_time = calc_route_time(obj, new_path, myAgent.my_map['cost_data'])
    old_path_1 = obj['move_path']
    old_path_2 = myAgent.map.gen_move_route(end_loc, move_loc, mod)
    old_path = old_path_1 + old_path_2
    old_path_time = calc_route_time(obj, old_path, myAgent.my_map['cost_data'])
    if new_path_time + 75 < old_path_time:
        return True
    else:
        return False


def move_n_shoot_check(obj, observation):
    """ 2020-5-9
    判断当前是否可以实施行进间射击
    """
    opr_v_a = observation['valid_actions'][obj['obj_id']]
    if 1 in opr_v_a.keys() and 2 in opr_v_a.keys():
        return True
    if 1 in opr_v_a.keys() and obj['weapon_cool_time'] <= 40:
        return True
    return False


def hit_check(obj_id, tar_id, move_shoot, myAgent):
    """我方算子是否可以有效“损伤”敌方算子的判断"""
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    cur_hex_ind = mapid_2_matind(obj['cur_hex'], map_size)
    enemy = myAgent.get_enemy(tar_id)
    pos_probs = myAgent.get_enemy_info(tar_id)['pos_probs']
    tar_type = enemy['type']
    tar_area_ind = np.arange(n_hexes)[pos_probs > 0]
    if move_shoot:  # 行进间射击的情况
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        fire_area_bool = (cost_matrix[cur_hex_ind] <= 60)
        fire_area = np.arange(n_hexes)[fire_area_bool]
        fire_area_att_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                                     fire_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                                     special_shoot='move_shoot')
        fire_area_attackabilities = fire_area_att_levels @ pos_probs[pos_probs > 0]
        attackabilities = np.max(fire_area_attackabilities)
    else:  # 普通射击的情况
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              cur_hex_ind, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot=None)
        attackabilities = attack_levels @ pos_probs[pos_probs > 0]
    return True if attackabilities > 0 else False


def hit_check_any_enemy(obj_id, move_shoot, myAgent):
    """我方算子当前是否可能实施有效射击的判断"""
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    n_hexes = map_size['row'] * map_size['col']
    if move_shoot:
        special_shoot = 'move_shoot'
    else:
        special_shoot = None
    obj = myAgent.get_bop(obj_id)
    obj_loc = obj['cur_hex']
    obj_loc_ind = mapid_2_matind(obj_loc, map_size)
    for enemy_info in myAgent.situations.enemies:
        enemy = enemy_info['operator']
        enemy_loc = enemy['cur_hex']
        enemy_loc_ind = mapid_2_matind(enemy_loc, map_size)
        shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], enemy['type'],
                                        special_shoot)
        if dist_matrix[obj_loc_ind, enemy_loc_ind] <= shoot_radius:
            if obj['type'] == BopType.Aircraft and enemy['type'] == BopType.Aircraft:
                see_matrix = myAgent.my_map['see_matrix'][1]
            elif obj['type'] != BopType.Aircraft and enemy['type'] != BopType.Aircraft:
                see_matrix = myAgent.my_map['see_matrix'][0]
            else:
                see_matrix = myAgent.my_map['see_matrix'][2]
            if move_shoot:
                cost_matrix = myAgent.my_map['cost_matrix'][0]
                fire_area_bool = (cost_matrix[obj_loc_ind] <= 60)
                fire_area = np.arange(n_hexes)[fire_area_bool]
                see_flag = see_matrix[enemy_loc_ind, fire_area]
                if np.any(see_flag):
                    return True
            else:
                if enemy['obj_id'] in obj['see_enemy_bop_ids']:
                    return True
    return False


def jm_salvo_check(arti_id, tar_loc, myAgent):
    """2020-7-1
    判断是否完成齐射动作
    """
    done_flag = False
    for jm_info in myAgent.observation['jm_points']:
        if jm_info['obj_id'] and jm_info['status'] == 0:  # 正在飞行的才算
            if jm_info['obj_id'] == arti_id and jm_info['pos'] == tar_loc:
                done_flag = True
                break
    return done_flag


def enemies_in_area(area, myAgent):
    """ 2020-4-30
    判断区域area中是否有敌方单位
    """
    enemy_ids = confirm_enemies(myAgent, 0)
    if not enemy_ids:  # 当前看不到敌人
        return False
    for id in enemy_ids:
        if myAgent.get_enemy_info(id)['operator']['cur_hex'] in area:
            return True
    return False


def enemy_around_city(city_loc, myAgent):
    """ 2020-6-24
    判断夺控点及周边六角格是否有敌方单位
    """
    map_size = myAgent.get_map_size()
    area = get_range(city_loc, 1, map_size)
    enemies = myAgent.situations.enemies
    for enemy_info in enemies:
        enemy_loc = enemy_info['operator']['cur_hex']
        if enemy_loc in area:
            return True
    return False


def get_remain_path(path, loc):
    """ 2020-4-26
    返回自loc以后的机动路线
    """
    path_remained = []
    for pt in path[::-1]:
        if pt != loc:
            path_remained.insert(0, pt)
        else:
            break
    return path_remained


#
# def infer_fire_loc(opr, area, myAgent):
#     """为算子opr推理出针对目标区域area的攻击点"""
#     if not area:  # 如果目标区域为空
#         return opr['cur_hex']
#
#     c_hex = closest_hex(opr['cur_hex'], area)
#     c_hex_offset = HexOffset(c_hex // 100, c_hex % 100)
#     directions = c_hex_offset.get_wedge_direction(HexOffset(opr['cur_hex'] // 100, opr['cur_hex'] % 100))
#
#     r_c = 10
#     while r_c > 5:
#         for direction in directions:
#             arc = c_hex_offset.get_arc(r_c, direction)
#             for hex in arc:
#                 if myAgent.map.can_see(hex, c_hex_offset, 0):  # 系统自带可是判断函数
#                     return hex
#         r_c = 10 + (10 - r_c) + 1 if r_c <= 10 else 10 - (r_c - 10)
#     return None


def get_cover_circles(area, radius):
    """ 2020-4-27
    对area的六角格区域，用半径为radius的圆进行全覆盖
    :param area: 六角格ID构成的list
    :param radius: 覆盖的圆半径
    :return: 用于全覆盖的圆心六角格构成的list
    """
    centers = []  # 用于输出
    num = len(area)
    dists = np.zeros((num, num), dtype=float)
    flags = np.ones(num, dtype=int)
    for m in range(num):
        for n in range(num):
            if m != n:
                dists[m, n] = cal_distance(area[m], area[n])
    while flags.sum() > 0:
        max_ind = (dists < 3).sum(1).argmax()
        inner_inds = dists[max_ind, :] < 3
        dists[inner_inds, :] = 1000
        dists[:, inner_inds] = 1000
        flags[inner_inds] = 0
        centers.append(area[max_ind])
    return centers


def get_cm_search_target(obj_id, enemies, myAgent):
    """ 2020-6-18 用于分队级别
    确定巡飞弹的搜索对象
    :param enemies: 在分队级想定中就是myAgent.situations.enemies；在群队级想定中需要划分出部分
    """
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    cm = myAgent.get_bop(obj_id)
    cm_loc_ind = mapid_2_matind(cm['cur_hex'], map_size)
    enemies_info = []
    for enemy_info in enemies:
        enemy = enemy_info['operator']
        if enemy['type'] != BopType.Aircraft:
            enemies_info.append(enemy_info)
    if not enemies_info:
        return None
    n_enemies = len(enemies_info)
    enemy_locs = np.zeros(n_enemies, dtype=int)
    enemy_uncertainties = np.zeros(n_enemies, dtype=float)  # 敌方算子的位置不确定性
    for m in range(n_enemies):
        enemy_info = enemies_info[m]
        enemy = enemy_info['operator']
        enemy_locs[m] = mapid_2_matind(enemy['cur_hex'], map_size)
        enemy_uncertainties[m] = 1 - np.max(enemy_info['pos_probs'])
    dists_2_enemies = dist_matrix[cm_loc_ind, enemy_locs]
    max_dist = np.max(dists_2_enemies)
    if max_dist < 1e-8:
        proximities = np.ones(n_enemies, dtype=float)
    else:
        proximities = (max_dist - dists_2_enemies) / max_dist  # 敌方算子所处位置的临近程度
    total_measures = 0.8 * enemy_uncertainties + 0.2 * proximities
    chosen_enemy = enemies_info[np.argmax(total_measures)]
    return chosen_enemy['obj_id']


def get_cm_search_target_2nd(obj_id, enemies, myAgent):
    """ 2020-7-6 用于群队级别
    确定巡飞弹的搜索对象
    :param enemies: 在分队级想定中就是myAgent.situations.enemies；在群队级想定中需要划分出部分
    """
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    cm = myAgent.get_bop(obj_id)
    cm_loc_ind = mapid_2_matind(cm['cur_hex'], map_size)
    # 确定其它巡飞弹的位置
    our_oprs = myAgent.situations.our_oprs
    other_cms, other_cms_locs = [], []
    for opr in our_oprs:
        if opr['sub_type'] == BopType.CruiseMissile and opr['on_board'] == 0 and opr['obj_id'] != obj_id:
            other_cms.append(opr)
            other_cms_locs.append(opr['cur_hex'])
    if other_cms:
        other_cms_locs_ind = mapid_2_matind(np.array(other_cms_locs), map_size)
    enemies_info = []
    for enemy_info in enemies:
        enemy = enemy_info['operator']
        if enemy['type'] != BopType.Aircraft or enemy['sub_type'] != BopType.Artillery:  # 炮兵和空中算子不跟踪
            enemies_info.append(enemy_info)
    if not enemies_info:
        return None
    n_enemies = len(enemies_info)
    enemy_locs = np.zeros(n_enemies, dtype=int)
    # === 敌方算子的位置不确定性 ===
    enemy_uncertainties = np.zeros(n_enemies, dtype=float)
    for m in range(n_enemies):
        enemy_info = enemies_info[m]
        enemy = enemy_info['operator']
        enemy_locs[m] = mapid_2_matind(enemy['cur_hex'], map_size)
        enemy_uncertainties[m] = 1 - np.max(enemy_info['pos_probs'])
    # === 敌方算子所处位置的临近程度 ===
    dists_2_enemies = dist_matrix[cm_loc_ind, enemy_locs]
    max_dist = np.max(dists_2_enemies)
    if max_dist < 1e-8:
        proximities = np.ones(n_enemies, dtype=float)
    else:
        proximities = (max_dist - dists_2_enemies) / max_dist
    # === 敌方算子到我方其它巡飞弹的疏远程度 ===
    if other_cms:
        dists_others_enemies = dist_matrix[other_cms_locs_ind, enemy_locs]
        min_dists_others_enemies = np.min(dists_others_enemies, axis=0)
        if np.max(min_dists_others_enemies) < 1e-8:
            remoteness = np.ones(n_enemies, dtype=float)
        else:
            remoteness = min_dists_others_enemies / np.max(min_dists_others_enemies)
    else:
        remoteness = np.ones(n_enemies, dtype=float)
    total_measures = 0.7 * enemy_uncertainties + 0.15 * proximities + 0.15 * remoteness
    chosen_enemy = enemies_info[np.argmax(total_measures)]
    return chosen_enemy['obj_id']


def be_seen(obj_id, myAgent):
    """ 2020-6-6
    感知最低级别的威胁，是否被发现
    """
    obj = myAgent.get_bop(obj_id)
    enemy_ids = obj['see_enemy_bop_ids']
    if not enemy_ids:
        return False
    for ene_id in enemy_ids:
        enemy = myAgent.get_enemy(ene_id)
        if can_see_obj(enemy, obj, myAgent.map):
            return True
    return False


def perceived_thread(obj_id, myAgent):
    """ 2020-5-2
    感知威胁，后期可以进一步根据数据分析进行智能化
    """
    obj = myAgent.get_bop(obj_id)
    enemies_id = confirm_enemies(myAgent, 2)  # 确定近期发现的敌人
    if obj['type'] == 1:  # 人员
        for ene_id in enemies_id:
            enemy = myAgent.get_enemy_info(ene_id)['operator']
            if enemy['type'] == 2:
                distance = cal_distance(obj['cur_hex'], enemy['cur_hex'])
                if myAgent.map.can_see(obj['cur_hex'], enemy['cur_hex'], 0):
                    if distance <= 10:
                        return True
                else:
                    if distance <= 7:  # 不通视的话，7格以内既是有危险
                        return True
    elif obj['type'] == 2:  # 车辆
        for ene_id in enemies_id:
            enemy = myAgent.get_enemy_info(ene_id)['operator']
            if enemy['type'] == 2:
                distance = cal_distance(obj['cur_hex'], enemy['cur_hex'])
                if distance <= 20:
                    return True
            elif enemy['type'] == 1:
                distance = cal_distance(obj['cur_hex'], enemy['cur_hex'])
                if distance <= 10:
                    return True
    return False


def confirm_defend_area(tar_hex, directions, radius, dist_matrix, map_size):
    """
    确定防守目标区域
    :param tar_hex: {int: map_id} 防守目标格
    :param directions: {list} 防守方向，支持传入int类型
    :param radius: {int} 防守半径
    :param dist_matrix: {narray} 距离矩阵
    :param map_size:
    :return: 防守目标区域，由mat_ind构造的向量
    """
    if isinstance(directions, int):
        directions = [directions]
    if radius <= 10:
        shift = 2
    else:
        shift = 4
    defend_area_1 = []
    for dir in directions:
        opposite_dir = (dir + 3) % 6
        delta_cube = Directions.Cube[opposite_dir].cub_multiply(shift)
        tar_hex_shift = hexoffset_2_mapid(mapid_2_hexoffset(tar_hex).cvt_2_cube().cube_add(delta_cube).cvt_2_offset())
        defend_area_1 += get_sector(tar_hex_shift, radius + shift, dir, map_size)
    defend_area_1 = np.array(list(set(defend_area_1)))  # 消除重复六角格，并转换成array格式
    # 删除防守区域中距离tar_hex六角格 4格 以内的所有六角格
    dist_vec = dist_matrix[mapid_2_matind(tar_hex, map_size)]
    defend_area_1_ind = mapid_2_matind(defend_area_1, map_size).astype('int64')
    defend_area_2_ind = defend_area_1_ind[dist_vec[defend_area_1_ind] > shift]
    return defend_area_2_ind


def infer_cm_path(cur_hex, area):
    """ 2020-5-7
    为巡飞弹规划出area区域中的巡逻路线，起始格和终止格是同一个格子
    """
    # 计算出半径为2的圆在area中的一个全覆盖，得到所有圆的圆心
    waypts = get_cover_circles(area, 2)
    # 找出距离cur_hex最近的一个圆
    start_pt = min(waypts, key=lambda x: cal_distance(x, cur_hex))
    # 将所有圆心按照启发式TSP问题连成一个环路
    circle = [start_pt]
    waypts.remove(start_pt)
    curr_pt = start_pt
    while waypts:
        next_pt = min(waypts, key=lambda x: cal_distance(x, curr_pt))
        circle.append(next_pt)
        waypts.remove(next_pt)
        curr_pt = next_pt
    circle.append(start_pt)
    # 用直线填充环路
    path_circ = [start_pt]
    circ_num = len(circle)
    for m in range(1, circ_num):
        hex_s, hex_t = circle[m - 1], circle[m]
        line = get_line(hex_s, hex_t)
        path_circ = path_circ + line
    return path_circ


def infer_cm_fire_path(cm_id, ene_id, myAgent):
    """ 2020-5-1
    根据敌方算子的历史信息，计算出巡飞弹的追击路线：起始格是当前格的下一格
    """
    ene_opr = myAgent.get_enemy_info(ene_id)['operator']
    ene_step = myAgent.get_enemy_info(ene_id)['step']
    ene_speed = ene_opr['basic_speed'] / 3.6
    map_size = myAgent.get_map_size()
    cur_step = myAgent.observation['time']['cur_step']
    cur_hex = myAgent.get_bop(cm_id)['cur_hex']
    cm_speed = 100 / 3.6
    distance = cal_distance(cur_hex, ene_opr['cur_hex']) * 200
    max_escape_len = ene_speed * (cur_step - ene_step)  # 丢失视野的时段，算子最大的逃离路程（米）
    hunt_radius = int((ene_speed * distance + cm_speed * max_escape_len) / (cm_speed - ene_speed) / 200)
    max_radius = int(myAgent.get_bop(cm_id)['alive_remain_time'] * cm_speed / 200)
    if hunt_radius <= max_radius:
        hunt_area = get_range(ene_opr['cur_hex'], hunt_radius, map_size)
    else:
        hunt_area = get_range(ene_opr['cur_hex'], max_radius, map_size)
    patrol_path = infer_cm_path(cur_hex, hunt_area)
    arrive_path = get_line(cur_hex, patrol_path[0])
    hunt_path = arrive_path[:-1] + patrol_path
    return hunt_path


# def infer_fire_loc(state, opr, area):


# def infer_off_loc(ifv_id, tar_loc, myAgent):
#     """ 2020-5-2
#     为步兵推理下车点
#     """
#     # 确定方向
#     enemies_id = confirm_enemies(myAgent, 3)
#     dir_of_enemies = []
#     for ene_id in enemies_id:
#         ene_loc = myAgent.get_enemy_info(ene_id)['operator']['cur_hex']
#         dir_to_tar_loc = get_direction(tar_loc, ene_loc)
#         if dir_to_tar_loc not in dir_of_enemies:
#             dir_of_enemies.append(dir_to_tar_loc)
#     ifv_obj = myAgent.get_bop(ifv_id)
#     direction = get_direction(tar_loc, ifv_obj['cur_hex'])
#     half_sphere = [(direction - 1) % 6, direction, (direction + 1) % 6]  # 目标点tar_loc的我方一侧
#     if 0 < len(dir_of_enemies) <= 3 and dir_of_enemies:
#         directions_off = [(d + 3) % 6 for d in dir_of_enemies]  # 敌人的反方向就是下车的方向
#     else:  # 敌人过于散布时，按我方位置确定
#         directions_off = half_sphere
#     # 初步确定可能的下车点，3格以内的half_sphere一侧
#     directions_hide = [(d + 3) % 6 for d in directions_off]  # 需要隐蔽的方向是下车的反方向
#     off_area = {}
#     for d in directions_off:
#         for r in range(1, 4):  # 范围在3个以内
#             arc = get_arc(tar_loc, r, d, myAgent.get_map_size())
#             for hex in arc:
#                 # 计算最大的速度乘子，便于机动的度量
#                 neighbors = myAgent.map.get_neighbors(hex)
#                 max_speed_multiplier = 0
#                 for nei in neighbors:
#                     if get_direction(hex, nei) in half_sphere:
#                         cur_multiplier = 1. / calc_speed_multiplier(nei, hex, 2, myAgent.my_map['cost_data'])
#                         if max_speed_multiplier < cur_multiplier:
#                             max_speed_multiplier = cur_multiplier
#                 # 计算隐蔽率，隐蔽性的度量
#                 hide_ratio = calc_hide_ratio(2, hex, directions_hide, myAgent)
#                 off_area[hex] = (r / 3., max_speed_multiplier, hide_ratio)  # (距离，机动，隐蔽)
#     # 一个好的下车点：离tar_loc越远越好(0.1)、便于机动(0.2)、不容易被发现(0.7)
#     off_loc = max(off_area, key=lambda x: off_area[x][0] * 0.1 + off_area[x][1] * 0.2 + off_area[x][2] * 0.7)
#     return off_loc


def infer_off_loc(ifv_id, tar_loc, myAgent):
    """ 2020-6-20
    为步兵推理下车点
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    dist_matrix = myAgent.my_map['dist_matrix']
    cost_matrix = myAgent.my_map['cost_matrix']
    ifv = myAgent.get_bop(ifv_id)
    ifv_loc_ind = mapid_2_matind(ifv['cur_hex'], map_size)
    city_flag = True if myAgent.get_city(tar_loc) else False
    tar_loc_ind = mapid_2_matind(tar_loc, map_size)
    if city_flag:
        off_area_bool = (dist_matrix[tar_loc_ind] <= 3)
    else:
        off_area_bool = (dist_matrix[tar_loc_ind] <= 2)
    off_area = np.arange(n_hexes)[off_area_bool]
    off_area_size = off_area.size
    # 1. 计算当前的安全程度
    threats = myAgent.situations.enemy_threat[0][off_area]
    max_threats, min_threats = np.max(threats), np.min(threats)
    if max_threats - min_threats < 1e-8:
        safty_norm = np.ones(off_area_size, dtype=float)
    else:
        safty_norm = (max_threats - threats) / (max_threats - min_threats)
    # 2. 计算易达程度
    move_costs = cost_matrix[0][ifv_loc_ind, off_area.reshape(1, -1)]
    max_costs, min_costs = np.max(move_costs), np.min(move_costs)
    if max_costs - min_costs < 1e-8:
        accessibilites = np.ones(off_area_size, dtype=float)
    else:
        accessibilites = (max_costs - move_costs) / (max_costs - min_costs)
    # 3. 计算到目标点的临近程度
    tar_dists = cost_matrix[1][tar_loc_ind, off_area.reshape(1, -1)]
    max_dist, min_dist = np.max(tar_dists), np.min(tar_dists)
    if max_dist - min_dist < 1e-8:
        proximities = np.ones(off_area_size, dtype=float)
    else:
        proximities = (max_dist - tar_dists) / (max_dist - min_dist)
    # 综合评估
    total_measures = 0.5 * safty_norm + 0.4 * accessibilites + 0.1 * proximities
    off_loc = matind_2_mapid(off_area[np.argmax(total_measures)], map_size)
    return off_loc


def infer_move_shoot_locs(obj_id, tar_id, myAgent):
    """ 2020-6-12
    推理行进间射击的射击点位和下一步机动点位
    :return: 输出是下一步的机动点位
    """
    if tar_id:
        if myAgent.get_enemy_info(tar_id):
            enemies = [myAgent.get_enemy_info(tar_id)]
        else:
            return None  # 没有必要执行行进间射击动作
    else:
        enemies = myAgent.situations.enemies
        if not enemies:
            return None  # 没有必要执行行进间射击动作
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hexes = int(map_size['row'] * map_size['col'])
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    enemy_threat = myAgent.situations.enemy_threat[0]
    cur_hex = obj['cur_hex']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    unit_time = 200. / (obj['basic_speed'] / 3.6)
    # ==== 计算火力圈的攻击效能 ====
    fire_circle_bool = (cost_matrix[cur_hex_ind] <= 4 * unit_time)
    fire_circle = np.arange(n_hexes)[fire_circle_bool]
    fire_circle_vol = fire_circle.size
    attackabilities_circle = np.zeros(fire_circle.size, dtype=float)
    for enemy_info in enemies:
        pos_probs = enemy_info['pos_probs']
        tar_area_ind = np.arange(n_hexes)[pos_probs > 0]
        tar_type = enemy_info['operator']['type']
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              fire_circle, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot='move_shoot')
        attackabilities_this = attack_levels @ pos_probs[tar_area_ind]
        attackabilities_circle = np.maximum(attackabilities_circle, attackabilities_this)
    if np.max(attackabilities_circle) < 1e-8:  # 4个范围内，没有有效的射击点位，保持不动，表示等待
        return cur_hex
    # ==== 计算下一点位的评估指标：攻击性和安全性 ====
    time_window = math.ceil(obj['weapon_cool_time'] / unit_time) * unit_time
    candidate_locs = np.array(get_range(cur_hex, 1, map_size))  # 下一点位的候选区域
    candidate_locs_ind = mapid_2_matind(candidate_locs, map_size)
    candidate_locs_size = candidate_locs.size
    attackabilities_candid = np.zeros(candidate_locs_size, dtype=float)  # 初始化攻击性
    # 1. 攻击性评估
    for m in range(candidate_locs_size):
        loc_ind = candidate_locs_ind[m]
        move_time_left = max(time_window - cost_matrix[cur_hex_ind, loc_ind], 0.)
        move_cost_fire_circle = np.maximum(cost_matrix[loc_ind][fire_circle_bool] - move_time_left,
                                           np.zeros(fire_circle_vol))
        attack_weights = move_cost_fire_circle / unit_time + 1.
        attackabilities_candid[m] = np.max(attackabilities_circle / (attack_weights ** 2))
    if np.max(attackabilities_candid) < 1e-8:
        attackabilities_norm = attackabilities_candid
    else:
        attackabilities_norm = attackabilities_candid / np.max(attackabilities_candid)
    # 2. 安全性评估
    # - 安全圈与火力圈相同，即safe_circle = fire_circle
    threat_base = enemy_threat[fire_circle]  # 威胁程度基数
    for m in range(fire_circle_vol):
        loc = matind_2_mapid(fire_circle[m], map_size)
        if loc == cur_hex:
            threat_base[m] *= 1.1  # “静止”的惩罚
        neighbors = myAgent.map.get_neighbors(loc) + [loc]
        other_opr_locs = [opr['cur_hex'] for opr in myAgent.situations.our_oprs if opr['obj_id'] != obj_id]
        if set(neighbors) & set(other_opr_locs):
            threat_base[m] *= 1.1  # “堆叠”的惩罚
    # - 确定安全圈中六角格的相邻性
    dissimilarities = cost_matrix[candidate_locs_ind.reshape(-1, 1), fire_circle.reshape(1, -1)]
    dissimilarities = np.maximum(dissimilarities / unit_time, np.ones(dissimilarities.shape, dtype=float))
    if abs(np.max(threat_base) - np.min(threat_base)) < 1e-8:
        circle_safty = np.ones(fire_circle_vol, dtype=float)
    else:
        circle_safty = (np.max(threat_base) - threat_base) / (np.max(threat_base) - np.min(threat_base))
    safty_norm = np.max(circle_safty.reshape(1, -1) / dissimilarities, axis=1)
    # 3. 综合评估
    total_measures = 0.8 * attackabilities_norm + 0.2 * safty_norm
    next_loc = candidate_locs[np.argmax(total_measures)]
    return next_loc


def infer_move_shoot_path(obj_id, myAgent):
    """ 2020-5-4
    推理行进间射击的射击点位和机动路径
    """
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hex = int(map_size['row'] * map_size['col'])
    if is_moving(obj):
        cur_hex = obj['move_path'][0]
        time_next_left = (1. - obj['cur_pos']) * 200. / (obj['basic_speed'] / 3.6)
        basic_time = ((obj['weapon_cool_time'] - time_next_left) // 20 + 1) * 20
    else:
        cur_hex = obj['cur_hex']
        basic_time = (obj['weapon_cool_time'] // 20 + 1) * 20
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    dist_matrix = myAgent.my_map['dist_matrix']
    see_matrix = myAgent.my_map['see_matrix'][0]
    # 确定隐蔽区域
    hide_area_inds = np.arange(n_hex)[cost_matrix[cur_hex_ind] <= basic_time + 80]
    tar_locs = np.arange(n_hex)[dist_matrix[cur_hex_ind] <= 25]
    enemy_loc_probs_full = np.zeros(n_hex)
    for enemy_info in myAgent.situations.enemies:
        enemy_loc_probs_full += predict_enemy_locs(myAgent, enemy_info)
    enemy_loc_probs = enemy_loc_probs_full[tar_locs]
    hide_ratio = 1 - calc_unhidden_ratio(obj['type'], hide_area_inds, tar_locs, see_matrix, dist_matrix,
                                         tar_loc_probs=enemy_loc_probs)  # 隐蔽区域的隐蔽性
    hide_ratio_full = np.zeros(n_hex)
    hide_ratio_full[hide_area_inds] = hide_ratio
    # 确定开火区域
    trigger_area_inds = np.arange(n_hex)[
        (cost_matrix[cur_hex_ind] <= basic_time + 20) & (cost_matrix[cur_hex_ind] >= basic_time)]
    n_trigger_locs = trigger_area_inds.size
    # 1.计算开火点位的攻击性
    n_enemy = len(myAgent.situations.enemies)
    predict_damages = np.zeros((n_trigger_locs, n_enemy))
    for m in range(n_enemy):
        enemy_info = myAgent.situations.enemies[m]
        enemy = enemy_info['operator']
        enemy_loc_probs_full = predict_enemy_locs(myAgent, enemy_info)
        enemy_locs = np.arange(n_hex)[enemy_loc_probs_full > 0]
        enemy_loc_probs = enemy_loc_probs_full[enemy_locs]
        if enemy_locs.size == 0:
            print("enemy['cur_hex']: ", enemy['cur_hex'])
            print("enemy_locs.shape: ", enemy_locs.shape)
            print("np.max(enemy_loc_probs_full): ", np.max(enemy_loc_probs_full))
        damages_enemy = calc_damage_common(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                           trigger_area_inds, enemy['type'], enemy_locs, obj['type'], obj['blood'],
                                           enemy['armor'], enemy['keep'], 'move_shoot')
        predict_damages[:, m] = np.sum(damages_enemy * np.tile(enemy_loc_probs, (n_trigger_locs, 1)), axis=1)
    if n_enemy == 0:
        attackability = np.ones(n_trigger_locs)
    elif n_enemy == 1:
        attackability = predict_damages[:, 0]
    else:
        attackability = np.mean(np.sort(predict_damages)[:, -2:], axis=1)
    if np.max(attackability) == np.min(attackability):
        attackability_norm = np.ones(n_trigger_locs)
    else:
        attackability_norm = (attackability - np.min(attackability)) / (np.max(attackability) - np.min(attackability))
    # 2.计算开火点位的隐蔽性
    candidate_hide_locs = cost_matrix[trigger_area_inds] * (
            (cost_matrix[trigger_area_inds] >= 40) & (cost_matrix[trigger_area_inds] <= 60) + 0)
    candidate_hide_locs[candidate_hide_locs > 0] = - candidate_hide_locs[
        candidate_hide_locs > 0] / 2000 + (1 + basic_time / 2000.)  # 40: 1, 60: 0.99
    hide_loc_inds = np.argmax(candidate_hide_locs * np.tile(hide_ratio_full, (n_trigger_locs, 1)), axis=1)
    invisibility = np.tile(hide_ratio_full, (n_trigger_locs, 1))[np.arange(n_trigger_locs), hide_loc_inds]
    if np.max(invisibility) == np.min(invisibility):
        invisibility_norm = np.ones(n_trigger_locs)
    else:
        invisibility_norm = (invisibility - np.min(invisibility)) / (np.max(invisibility) - np.min(invisibility))
    # 3.机动耗时越短越好
    move_cost = cost_matrix[cur_hex_ind, trigger_area_inds] + cost_matrix[trigger_area_inds, hide_loc_inds]
    if np.max(move_cost) == np.min(move_cost):
        move_cost_norm = np.ones(n_trigger_locs)
    else:
        move_cost_norm = (np.max(move_cost) - move_cost) / (np.max(move_cost) - np.min(move_cost))
    # 4.离敌方越近越好
    avg_dist_enemies = np.sum(cost_matrix[trigger_area_inds] * np.tile(enemy_loc_probs_full, (n_trigger_locs, 1)),
                              axis=1)
    if np.max(avg_dist_enemies) == np.min(avg_dist_enemies):
        avg_dist_enemies_norm = np.ones(n_trigger_locs)
    else:
        avg_dist_enemies_norm = (np.max(avg_dist_enemies) - avg_dist_enemies) / (
                np.max(avg_dist_enemies) - np.min(avg_dist_enemies))
    # 综合计算
    total_measure = 0.4 * attackability_norm + 0.4 * invisibility_norm + 0.19 * move_cost_norm + 0.01 * avg_dist_enemies_norm
    best_fire_loc_ind = trigger_area_inds[np.argmax(total_measure)]
    best_hide_loc_ind = hide_loc_inds[trigger_area_inds == best_fire_loc_ind][0]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    best_hide_loc = matind_2_mapid(best_hide_loc_ind, map_size)
    # 计算机动路径，并输出
    path = myAgent.map.gen_move_route(cur_hex, best_fire_loc, 0) + myAgent.map.gen_move_route(best_fire_loc,
                                                                                              best_hide_loc, 0)
    return path, best_fire_loc


def infer_fire_loc(obj_id, tar_area, tar_type, myAgent, move_shoot=False):
    """ 2020-6-8
    根据敌方所在位置推理算子的攻击点
    主要考虑的因素：对目标区域的火力覆盖水平、攻击点的安全性、出发地临近程度
    :param tar_area: 2种取值，1）list，由六角格（map_id）构成，各个格子概率相同；
                              2）np.array，n_hexes长度的一维数组（mat_ind），表示对应六角格的概率（pos_probs）
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    if isinstance(tar_area, list):
        tar_area_ind = mapid_2_matind(np.array(tar_area), map_size)
        pos_probs = np.ones(len(tar_area)) / len(tar_area)
    else:
        tar_area_ind = np.arange(n_hexes)[tar_area > 0]
        pos_probs = tar_area[tar_area > 0]
    obj = myAgent.get_bop(obj_id)
    # obj = obj_id  # 调试用
    cur_hex = obj['cur_hex']
    obj_type = obj['type']
    obj_speed = obj['basic_speed']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:  # 空中算子
        cost_matrix = dist_matrix * (200. / obj_speed * 3.6)
    # 确定推理模式
    precise_infer, rough_infer = range(2)
    if obj_type == BopType.Vehicle:
        threat_situation = myAgent.situations.enemy_threat[0]
        neighbors_bool = (cost_matrix[cur_hex_ind] <= 2 * (200. / obj_speed * 3.6))
        if np.max(threat_situation[neighbors_bool]) > 0:
            infer_mod = precise_infer  # 使用当前threat_situation确定安全性
        else:
            infer_mod = rough_infer  # 根据地形确定安全性
    elif obj_type == BopType.Infantry:
        threat_situation = myAgent.situations.enemy_threat[1]
        neighbors_bool = (cost_matrix[cur_hex_ind] <= 1 * (200. / obj_speed * 3.6))
        if np.max(threat_situation[neighbors_bool]) > 0:
            infer_mod = precise_infer  # 使用当前threat_situation确定安全性
        else:
            infer_mod = rough_infer  # 根据地形确定安全性
    elif obj['sub_type'] == BopType.Helicopter:  # 空中算子一定是Precise_infer
        threat_situation = myAgent.situations.enemy_threat[2]
        infer_mod = precise_infer
    else:
        threat_situation = myAgent.situations.enemy_threat[3]
        infer_mod = precise_infer
    # 初步筛选出可能的攻击点位
    special_shoot = 'move_shoot' if move_shoot else None
    shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], tar_type, special_shoot)
    possible_shoot_area_bool = np.any(dist_matrix[tar_area_ind] <= shoot_radius, axis=0)
    possible_shoot_area = np.arange(n_hexes)[possible_shoot_area_bool]
    # 1. 计算攻击性
    if move_shoot:  # 行进间射击的情况
        fire_area_bool = np.any(cost_matrix[possible_shoot_area_bool] <= 40, axis=0)
        fire_area = np.arange(n_hexes)[fire_area_bool]
        fire_area_att_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                                     fire_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                                     special_shoot='move_shoot')
        fire_area_attackabilities = fire_area_att_levels @ pos_probs
        fire_area_attackabilities_full = np.zeros(n_hexes)
        fire_area_attackabilities_full[fire_area] = fire_area_attackabilities
        attackabilities = np.max(
            ((cost_matrix[possible_shoot_area] <= 40) + 0) * np.tile(fire_area_attackabilities_full,
                                                                     (possible_shoot_area.size, 1)), axis=1)
    else:  # 普通射击的情况
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              possible_shoot_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot=None)
        attackabilities = attack_levels @ pos_probs
    max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
    if max_attack - min_attack < 1e-8:
        attackabilities_norm = np.ones(possible_shoot_area.size)
    else:
        attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
    # 2. 计算安全性（使用威胁态势进行计算）
    if infer_mod == precise_infer:
        threat = threat_situation[possible_shoot_area]
        max_threat, min_threat = np.max(threat), np.min(threat)
        safty_norm = (max_threat - threat) / (max_threat - min_threat)
    else:
        if move_shoot:  # 行进间射击的情况，用六角格的隐蔽性代替安全性
            see_matrix = myAgent.my_map['see_matrix'][0]
            safty_norm = 1 - calc_unhidden_ratio(obj_type, possible_shoot_area, tar_area_ind, see_matrix, dist_matrix,
                                                 distance=None, tar_loc_probs=pos_probs)
        else:  # 普通射击的情况，只考虑六角格地形的安全性
            if obj_type == BopType.Infantry:
                terrain_rect = np.array(TerrainMeanRect.Soldier)
            else:
                terrain_rect = np.array(TerrainMeanRect.Vehicle[obj['armor']])
            terrain_rect_norm = (np.max(terrain_rect) - terrain_rect) / (np.max(terrain_rect) - np.min(terrain_rect))
            safty_norm = terrain_rect_norm[myAgent.my_map['terrain_vec'][possible_shoot_area]]
    # 3. 计算出发地临近程度
    move_costs = cost_matrix[cur_hex_ind][possible_shoot_area]
    max_move_costs, min_move_costs = np.max(move_costs), np.min(move_costs)
    if max_move_costs - min_move_costs < 1e-8:
        proximities = np.ones(possible_shoot_area.size)
    else:
        proximities = (max_move_costs - move_costs) / (max_move_costs - min_move_costs)
    # 综合计算
    total_measure = 0.6 * attackabilities_norm + 0.3 * safty_norm + 0.1 * proximities
    best_fire_loc_ind = possible_shoot_area[np.argmax(total_measure)]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    return best_fire_loc


def infer_targeted_fire_loc(obj_id, tar_id, myAgent, move_shoot=False):
    """ 2020-6-16
    针对敌方算子tar_id推理算子obj_id的攻击点
    主要考虑的因素：对目标区域的火力覆盖水平、攻击点的安全性、出发地临近程度
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    enemy_info = myAgent.get_enemy_info(tar_id)
    if not enemy_info:
        return None  # 表示敌算子已死亡
    enemy = enemy_info['operator']
    tar_type = enemy['type']
    pos_probs_full = enemy_info['pos_probs']
    tar_area_ind = np.arange(n_hexes)[pos_probs_full > 0]
    pos_probs = pos_probs_full[pos_probs_full > 0]
    obj = myAgent.get_bop(obj_id)
    cur_hex = obj['cur_hex']
    obj_type = obj['type']
    obj_speed = obj['basic_speed']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:  # 空中算子
        cost_matrix = dist_matrix * (200. / obj_speed * 3.6)
    # 选择威胁态势
    if obj_type == BopType.Vehicle:
        threat_situation = myAgent.situations.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        threat_situation = myAgent.situations.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        threat_situation = myAgent.situations.enemy_threat[2]
    else:
        threat_situation = myAgent.situations.enemy_threat[3]
    # 初步筛选出可能的攻击点位
    special_shoot = 'move_shoot' if move_shoot else None
    possible_shoot_area = get_enemy_shooted_range(enemy, obj, myAgent, special_shoot)
    # 1. 计算攻击性
    attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                          possible_shoot_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                          special_shoot=None)
    attackabilities = attack_levels @ pos_probs
    max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
    if max_attack - min_attack < 1e-8:
        attackabilities_norm = np.ones(possible_shoot_area.size)
    else:
        attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
    # 2. 计算安全性（使用威胁态势进行计算）
    threat = threat_situation[possible_shoot_area]
    max_threat, min_threat = np.max(threat), np.min(threat)
    safty_norm = (max_threat - threat) / (max_threat - min_threat)
    # 3. 计算出发地临近程度
    move_costs = cost_matrix[cur_hex_ind][possible_shoot_area]
    max_move_costs, min_move_costs = np.max(move_costs), np.min(move_costs)
    if max_move_costs - min_move_costs < 1e-8:
        proximities = np.ones(possible_shoot_area.size)
    else:
        proximities = (max_move_costs - move_costs) / (max_move_costs - min_move_costs)
    # 综合计算
    total_measure = 0.2 * attackabilities_norm + 0.5 * safty_norm + 0.3 * proximities
    best_fire_loc_ind = possible_shoot_area[np.argmax(total_measure)]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    return best_fire_loc


def infer_safe_loc(obj, area, myAgent):
    """ 2020-5-5
    为算子obj在区域area中推理出最为安全的格子
    """
    cur_hex = obj['cur_hex']
    if obj['type'] == 1:  # 人员
        forest_area = []
        resident_area = []
        other_area = []
        for hex in area:
            terrain = get_terrain_info(myAgent.map.get_map_data(), hex)
            if terrain == 1:  # 优先选择丛林地形
                forest_area.append(hex)
            elif terrain == 2:  # 其次选择居民地地形
                resident_area.append(hex)
            else:
                other_area.append(hex)
        if forest_area:
            return get_closest_hex(cur_hex, forest_area)
        elif resident_area:
            return get_closest_hex(cur_hex, resident_area)
        else:  # 普通地形主要考虑隐蔽性
            best_hide_ratio = 0
            best_hex = area[0]
            for hex in area:
                h_r = calc_hide_ratio(obj_type=1, cur_hex=hex, directions=list(range(6)),
                                      myAgent=myAgent)
                if best_hide_ratio < h_r:
                    best_hide_ratio = h_r
                    best_hex = hex
            return best_hex
    else:  # 非人员算子暂时没有涉及
        return area[0]


def infer_defend_loc(obj, tar_hex, directions, myAgent):
    """ 2020-5-24
    为算子obj针对方向directions进行防守点位的确定
    obj的类型只可能是“车辆”
    """
    dist_matrix = myAgent.my_map['dist_matrix']
    map_size = myAgent.get_map_size()
    n_hex = int(map_size['row'] * map_size['col'])
    if obj['type'] == BopType.Aircraft:
        see_matrix = myAgent.my_map['see_matrix'][1]
    else:
        see_matrix = myAgent.my_map['see_matrix'][0]
    # 确定防守目标区域
    thread_area_inds = confirm_defend_area(tar_hex, directions, 20, dist_matrix, map_size)
    # 确定防守点范围
    if obj['type'] == BopType.Infantry:
        radius = 1  # 根据机动耗时，选择防守点范围
    elif obj['type'] == BopType.Vehicle:
        radius = 10
    elif obj['type'] == BopType.Aircraft:
        radius = 20
    else:
        raise Exception('目标算子类型输入错误！')
    hex_inds = np.arange(n_hex)
    tar_hex_ind = mapid_2_matind(tar_hex, map_size)
    defend_locs_inds = hex_inds[dist_matrix[tar_hex_ind] <= radius]  # 防守点范围
    # 区分“坦克”和“非坦克”2中情况
    if obj['sub_type'] != BopType.Tank:
        # 1.计算攻击性
        attack_radius = 0
        for wp_id in obj['carry_weapon_ids']:
            weapon = get_weapon(wp_id)
            if obj['remain_bullet_nums'][weapon.type] > 0:
                if attack_radius < max(weapon.shoot_range.values()):
                    attack_radius = max(weapon.shoot_range.values())  # 确定防守算子的攻击半径
        unhidden_ratio = calc_unhidden_ratio(obj['type'], defend_locs_inds, thread_area_inds, see_matrix, dist_matrix,
                                             attack_radius)
        attackability = (unhidden_ratio - np.min(unhidden_ratio)) / (np.max(unhidden_ratio) - np.min(unhidden_ratio))
        # 2.计算安全性
        enemy_weapon_ids = get_rival_weapons(myAgent, obj['type'])  # 敌方所有武器
        remain_bullet_nums = {0: 100, 100: 100, 101: 100, 102: 100}  # 保证所有弹药都可用
        damages = calc_damage_common(myAgent, enemy_weapon_ids, remain_bullet_nums, thread_area_inds, obj['type'],
                                     defend_locs_inds, att_blood=obj['blood'], armor_type=obj['armor'])
        denominator_damages = np.sum(damages > 0, axis=0)
        denominator_damages[denominator_damages == 0] = 1
        unit_damages = np.sum(damages, axis=0) / denominator_damages
        avg_damage = np.max(unit_damages[np.logical_not(np.isnan(unit_damages))])
        unit_damages[np.isnan(unit_damages)] = avg_damage
        if np.max(unit_damages) - np.min(unit_damages) < 1e-8:
            safty = np.ones(unit_damages.size)
        else:
            safty = (np.max(unit_damages) - unit_damages) / (np.max(unit_damages) - np.min(unit_damages))
        # 3.计算临近性：到当前位置的距离
        distances = dist_matrix[mapid_2_matind(obj['cur_hex'], map_size), defend_locs_inds.reshape(1, -1)].reshape(-1)
        proximity = (np.max(distances) - distances) / (np.max(distances) - np.min(distances))
        # 综合计算
        total_measure = 0.3 * attackability + 0.5 * safty + 0.2 * proximity
    else:  # 坦克的情况（行进间射击）
        # 1.计算攻击性
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        attack_locs_each = (cost_matrix[defend_locs_inds] >= 40) & (cost_matrix[defend_locs_inds] <= 60)
        attack_loc_area = hex_inds[np.any(attack_locs_each, axis=0)]  # 攻击点位构成的区域
        attack_radius = 0
        for wp_id in obj['carry_weapon_ids']:
            weapon = get_weapon(wp_id)
            if obj['remain_bullet_nums'][weapon.type] > 0 and weapon.type == BulletType.Convention:
                if attack_radius < max(weapon.shoot_range.values()):
                    attack_radius = max(weapon.shoot_range.values())  # 确定防守算子的攻击半径
        unhidden_ratio_att = calc_unhidden_ratio(obj['type'], attack_loc_area, thread_area_inds, see_matrix,
                                                 dist_matrix, attack_radius)
        attack_locs_attackability = np.zeros(n_hex)
        attack_locs_attackability[attack_loc_area] = unhidden_ratio_att
        defend_locs_attackability = np.max(
            attack_locs_each * np.tile(attack_locs_attackability, (len(defend_locs_inds), 1)), axis=1)
        attackability = (defend_locs_attackability - np.min(defend_locs_attackability)) / (
                np.max(defend_locs_attackability) - np.min(defend_locs_attackability))
        # 2.计算隐蔽性（0.4）
        unhidden_ratio = calc_unhidden_ratio(obj['type'], defend_locs_inds, thread_area_inds, see_matrix, dist_matrix)
        invisibility = (np.max(unhidden_ratio) - unhidden_ratio) / (np.max(unhidden_ratio) - np.min(unhidden_ratio))
        # 3.计算临近性（0.1）：到当前位置的距离
        distances = dist_matrix[mapid_2_matind(obj['cur_hex'], map_size), defend_locs_inds.reshape(1, -1)].reshape(-1)
        proximity = (np.max(distances) - distances) / (np.max(distances) - np.min(distances))
        # 综合计算
        total_measure = 0.3 * attackability + 0.5 * invisibility + 0.2 * proximity
    best_defend_loc_ind = defend_locs_inds[np.argmax(total_measure)]
    best_defend_loc = matind_2_mapid(best_defend_loc_ind, map_size)
    return best_defend_loc


def infer_support_loc(obj_id, tar_area, myAgent):
    """ 2020-5-7
    为战车算子obj计算出针对目标区域tar_area的引导射击支援点位
    """
    ifv = myAgent.get_bop(obj_id)
    # ifv = obj_id  # 调试用
    cur_hex = ifv['cur_hex']
    distance = 20  # 重型导弹的射击范围是20
    hex_farrest = get_farrest_hex(cur_hex, tar_area)
    # 1. 从目标区域每个格子的通视区域，初步筛选出可能的支援点位
    max_distance = int(calc_max_distance(cur_hex, tar_area))  # 计算当前位置cur_hex到tar_area的最大距离
    area_1 = get_range(cur_hex, max_distance, myAgent.get_map_size())  # 所有候选点落在这个圆内
    possible_support_area = []  # 根据目标格，初步筛选出支援点的范围
    for hex in area_1:
        if hex not in tar_area:
            if cal_distance(hex, hex_farrest) <= distance:
                possible_support_area.append(hex)
    # 2. 对于possible_support_area中的每个格子，度量它们的隐蔽性、到tar_area的平均距离、到出发点cur_hex的距离
    support_measures = {}
    max_max_distance = 0
    max_proximity = 0
    for hex in possible_support_area:
        max_distance = calc_max_distance(hex, tar_area)
        if max_distance <= distance:
            hide_ratio = calc_hide_ratio_2(2, hex, tar_area, myAgent.map)
            if max_max_distance < max_distance:
                max_max_distance = max_distance
            distance_to_curhex = cal_distance(cur_hex, hex)
            if max_proximity < distance_to_curhex:
                max_proximity = distance_to_curhex
            support_measures[hex] = (hide_ratio, max_distance, distance_to_curhex)
    # 3. 对各六角格的隐蔽性(0.8)、（标准化后）最远距离(0.1)、到出发点的临近度进行加权(0.1)，实现支援点位的选取
    support_loc = max(support_measures, key=lambda x: 0.8 * support_measures[x][0] +
                                                      0.1 * support_measures[x][1] / max_max_distance +
                                                      0.1 * (max_proximity - support_measures[x][2]) / max_proximity)
    return support_loc


def infer_scout_loc(obj_id, tar_area, myAgent):
    """ 2020-5-7
    为战车算子obj计算出针对目标区域tar_area的侦察点位
    """
    obj = myAgent.get_bop(obj_id)
    cur_hex = obj['cur_hex']
    hex_farrest = get_farrest_hex(cur_hex, tar_area)
    distance = 25
    # 1. 从目标区域每个格子的通视区域，初步筛选出可能的侦察点位
    avg_distance = int(calc_avg_distance(cur_hex, tar_area))  # 计算当前位置cur_hex到tar_area的平均距离
    area_1 = get_range(cur_hex, avg_distance, myAgent.get_map_size())  # 所有候选点落在这个圆内
    possible_scout_area = []  # 根据目标格，初步筛选出支援点的范围
    for hex in area_1:
        if hex not in tar_area:
            if cal_distance(hex, hex_farrest) <= distance:
                possible_scout_area.append(hex)
    # 2. 对于possible_scout_area中的每个格子，度量它们的观察能力、安全地形、到tar_area的平均距离
    scout_measures = {}
    max_avg_distance = 0
    max_dist_start = 0
    for hex in possible_scout_area:
        observation = 1 - calc_hide_ratio_2(2, hex, tar_area, myAgent.map)
        terrain = get_terrain_info(myAgent.map.get_map_data(), hex)
        if terrain == 1 or terrain == 2:
            safe_terrain = 1
        else:
            safe_terrain = 0
        avg_distance = calc_avg_distance(hex, tar_area)
        if max_avg_distance < avg_distance:
            max_avg_distance = avg_distance
        dist_to_start = cal_distance(cur_hex, hex)
        if max_dist_start < dist_to_start:
            max_dist_start = dist_to_start
        scout_measures[hex] = (observation, safe_terrain, avg_distance, dist_to_start)
    # 3. 对各六角格的观察能力(0.3)、安全地形(0.2)、到tar_area的平均距离(0.3)、距出发点距离(0.2)进行加权平均，实现侦察点位的选取
    scout_loc = max(scout_measures, key=lambda x: 0.4 * scout_measures[x][0] +
                                                  0.2 * scout_measures[x][1] +
                                                  0.2 * scout_measures[x][2] / max_avg_distance +
                                                  0.2 * (max_dist_start - scout_measures[x][3]) / max_dist_start)
    return scout_loc


def infer_unload_loc(ifv_id, myAgent):
    """ 2020-5-19
    推理附近安全的下车点（无人战车、巡飞弹）
    """
    ifv = myAgent.get_bop(ifv_id)
    map_size = myAgent.get_map_size()
    map_hex_num = map_size['row'] * map_size['col']
    radius = 3  # 搜索半径，基于basic_speed的标准地形
    time_limit = 200. / (ifv['basic_speed'] / 3.6) * radius
    # 1.在time_limit的限定下，确定搜索范围search_range
    if ifv['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif ifv['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        raise Exception('本函数不适用于当前类型的算子！')
    c_hex = ifv['move_path'][0] if is_moving(ifv) else ifv['cur_hex']
    c_ind = mapid_2_matind(c_hex, map_size)
    search_range = np.arange(map_hex_num)[cost_matrix[c_ind, :] <= time_limit]  # 用mat_ind表示
    # 2.计算search_range中六角格在当前态势下的隐蔽性
    enemies_id = confirm_enemies(myAgent, radius=2)
    enemies_loc = []
    for ene_id in enemies_id:
        enemies_loc.append(myAgent.get_enemy_info(ene_id)['operator']['cur_hex'])
    if enemies_loc:
        tar_area = mapid_2_matind(enemies_loc, map_size)
        hide_ratio = 1 - calc_unhidden_ratio(ifv['type'], search_range, tar_area, myAgent.my_map['see_matrix'][0],
                                             myAgent.my_map['dist_matrix'])
    else:
        hide_ratio = np.ones(len(search_range))  # 没有敌人时，都有最好的隐蔽性
    # 3.计算search_range中六角格距中心六角格c_hex的最短机动耗时
    cost_time = cost_matrix[c_ind][search_range]
    cost_time_norm = (np.max(cost_time) - cost_time) / (np.max(cost_time) - np.min(cost_time))
    # 4.综合评估：隐蔽性（0.8），机动性（0.2）
    total_result = 0.8 * hide_ratio + 0.2 * cost_time_norm
    # 5.根据综合评估选择出最佳下车点，如有多个，随机选择
    best_loc = matind_2_mapid(np.random.choice(search_range[total_result == np.max(total_result)]), map_size)
    return best_loc


def infer_counterattack_loc(obj_id, myAgent):
    """ 2020-5-20
    推理有组织机动过程中的反击点位
    """
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    map_hex_num = map_size['row'] * map_size['col']
    radius = 3  # 搜索半径，基于basic_speed的标准地形
    weapons_id = obj['carry_weapon_ids']
    tar_type = BopType.Vehicle  # 目前只考虑目标是车辆，后期修改
    distance = 0
    for wp_id in weapons_id:
        dist_wp = get_weapon(wp_id).shoot_range[tar_type]
        if distance < dist_wp:
            distance = dist_wp
    # 1.在radius的限定下，确定搜索范围search_range
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        raise Exception('本函数不适用于当前类型的算子！')
    c_hex = obj['move_path'][0] if is_moving(obj) else obj['cur_hex']
    c_ind = mapid_2_matind(c_hex, map_size)
    search_range = np.arange(map_hex_num)[dist_matrix[c_ind, :] <= radius]  # 用mat_ind表示
    # 2.计算search_range中六角格的攻击效能
    enemies_id = confirm_enemies(myAgent, radius=2)
    enemies_loc = []
    for ene_id in enemies_id:
        enemy = myAgent.get_enemy_info(ene_id)['operator']
        if myAgent.map.get_distance(enemy['cur_hex'], obj['cur_hex']) <= distance:
            enemies_loc.append(myAgent.get_enemy_info(ene_id)['operator']['cur_hex'])
    if enemies_loc:
        tar_area = mapid_2_matind(enemies_loc, map_size)
        fire_effect = calc_fire_effect(obj, search_range, tar_area, distance, myAgent.my_map['see_matrix'][0],
                                       myAgent.my_map['dist_matrix'])
    else:
        fire_effect = np.ones(len(search_range))  # 没有敌人时，都有最好的攻击效能
    # 3.计算search_range中六角格的安全性
    safty = np.zeros(len(search_range))
    for m in range(len(safty)):
        safty[m] = calc_safty(matind_2_mapid(search_range[m], map_size), obj['type'], obj['armor'],
                              myAgent.map)
    # 4.计算search_range中六角格距中心六角格c_hex的最短机动耗时
    cost_time = cost_matrix[c_ind][search_range]
    cost_time_norm = (np.max(cost_time) - cost_time) / (np.max(cost_time) - np.min(cost_time))
    # 5.综合评估：攻击效能（0.5），安全性（0.4），机动性（0.1）
    total_result = 0.5 * fire_effect + 0.4 * safty + 0.1 * cost_time_norm
    # 根据综合评估选择出最佳下车点，如有多个，随机选择
    best_loc = matind_2_mapid(np.random.choice(search_range[total_result == np.max(total_result)]), map_size)
    return best_loc


def infer_fire_propulsion_loc(obj_id, myAgent):
    """2020-6-22
    为“火力推进任务”推理火力输出点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    enemies = myAgent.situations.enemies
    n_enemies = len(enemies)
    if n_enemies <= 0:  # 没有敌人，没有必要火力推进
        return None
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    # 计算点位候选区域和各候选点位对敌方单位的最大攻击伤害
    candidate_locs_bool = np.zeros(n_hexes, dtype=bool)
    for enemy_info in enemies:
        enemy = enemy_info['operator']
        if enemy['type'] == BopType.Aircraft or enemy['sub_type'] == BopType.Artillery:  # 火力推进的目标不是“空中单位”和“炮兵”
            continue
        this_range = get_enemy_shooted_range(enemy, obj, myAgent)
        candidate_locs_bool[this_range] = True
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    n_candidates = candidate_locs.size
    # 1. 计算候选点位的安全性
    if obj_type == BopType.Vehicle:
        enemy_threats = myAgent.situations.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        enemy_threats = myAgent.situations.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        enemy_threats = myAgent.situations.enemy_threat[2]
    else:
        enemy_threats = myAgent.situations.enemy_threat[3]
    candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
    max_threat, min_threat = np.max(candidate_threats), np.min(candidate_threats)
    if max_threat - min_threat < 1e-8:
        safty_norm = np.ones(n_candidates, dtype=float)
    else:
        safty_norm = (max_threat - candidate_threats) / max_threat
    # 2. 计算算子当前位置到各候选点位的易达性
    obj_loc = mapid_2_matind(obj['cur_hex'], map_size)
    if obj_type == BopType.Vehicle:
        move_costs = myAgent.my_map['cost_matrix'][0][obj_loc, candidate_locs]
    elif obj_type == BopType.Infantry:
        move_costs = myAgent.my_map['cost_matrix'][1][obj_loc, candidate_locs]
    else:
        move_costs = myAgent.my_map['dist_matrix'][obj_loc, candidate_locs]
    max_cost, min_cost = np.max(move_costs), np.min(move_costs)
    if max_cost - min_cost < 1e-8:
        accessibility_norm = np.ones(n_candidates, dtype=float)
    else:
        accessibility_norm = (max_cost - move_costs) / (max_cost - min_cost)
    # 综合评估，选点位
    if obj_type == BopType.Vehicle:
        total_measures = 0.7 * safty_norm + 0.3 * accessibility_norm
    elif obj_type == BopType.Infantry:
        total_measures = 0.2 * safty_norm + 0.8 * accessibility_norm
    else:
        total_measures = 0.9 * safty_norm + 0.1 * accessibility_norm
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_guide_fire_loc(obj_id, myAgent):
    """2020-6-24
    为UCV和UAV推理引导射击点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    enemies = myAgent.situations.enemies
    n_enemies = len(enemies)
    if n_enemies <= 0:  # 没有敌人，没有必要火力推进
        return None
    # 计算点位候选区域
    candidate_locs_bool = np.zeros(n_hexes, dtype=bool)
    for enemy_info in enemies:
        enemy = enemy_info['operator']
        if enemy['type'] != BopType.Vehicle:  # 引导射击的目标只能是战车和坦克
            continue
        if enemy['sub_type'] == BopType.Artillery:
            continue
        this_range = get_enemy_observed_range(enemy, obj, myAgent)
        candidate_locs_bool[this_range] = True
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    n_candidates = candidate_locs.size
    # 1. 计算候选点位的安全性
    if obj_type == BopType.Vehicle:
        enemy_threats = myAgent.situations.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        enemy_threats = myAgent.situations.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        enemy_threats = myAgent.situations.enemy_threat[2]
    else:
        enemy_threats = myAgent.situations.enemy_threat[3]
    candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
    max_threat, min_threat = np.max(candidate_threats), np.min(candidate_threats)
    if max_threat - min_threat < 1e-8:
        safty_norm = np.ones(n_candidates, dtype=float)
    else:
        safty_norm = (max_threat - candidate_threats) / max_threat
    # 2. 计算算子当前位置到各候选点位的易达性
    obj_loc = mapid_2_matind(obj['cur_hex'], map_size)
    if obj_type == BopType.Vehicle:
        move_costs = myAgent.my_map['cost_matrix'][0][obj_loc, candidate_locs]
    elif obj_type == BopType.Infantry:
        move_costs = myAgent.my_map['cost_matrix'][1][obj_loc, candidate_locs]
    else:
        move_costs = myAgent.my_map['dist_matrix'][obj_loc, candidate_locs]
    max_cost, min_cost = np.max(move_costs), np.min(move_costs)
    if max_cost - min_cost < 1e-8:
        accessibility_norm = np.ones(n_candidates, dtype=float)
    else:
        accessibility_norm = (max_cost - move_costs) / (max_cost - min_cost)
    # 综合评估，选点位
    if obj_type == BopType.Vehicle:
        total_measures = 0.7 * safty_norm + 0.3 * accessibility_norm
    elif obj_type == BopType.Infantry:
        total_measures = 0.4 * safty_norm + 0.6 * accessibility_norm
    else:
        total_measures = 0.9 * safty_norm + 0.1 * accessibility_norm
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_directing_loc(obj_id, tar_loc, remaining_steps, myAgent):
    """2020-7-3（未调试）
    为炮兵校射算子推理校射点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    time_threshold = remaining_steps * 0.9
    obj = myAgent.get_bop(obj_id)
    obj_loc_ind = mapid_2_matind(obj['cur_hex'], map_size)
    obj_type = obj['type']
    # 确定cost_matrix
    if obj['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        cost_matrix = myAgent.my_map['dist_matrix'] * 200. / (obj['basic_speed'] / 3.6)
    # === 确定tar_loc中的敌方算子 ===
    tar_enemies = []
    for enemy_info in myAgent.situations.enemies:
        enemy = enemy_info['operators']
        ene_loc = enemy['cur_hex']
        if enemy['type'] != BopType.Aircraft:
            if enemy['armor'] != ArmorType.Composit:
                if ene_loc == tar_loc:
                    if np.max(enemy_info['pos_probs']) > 1 - 1e-8:
                        tar_enemies.append(enemy)
    if not tar_enemies:
        return None
    # === 确定观察范围 ===
    if len(tar_enemies) == 1:
        observe_range_bool = get_enemy_observed_range(tar_enemies[0], obj, myAgent)
    else:
        observe_range_bool = np.ones(n_hexes, dtype=bool)
        for enemy in tar_enemies:
            observe_range_bool = np.logical_and(observe_range_bool, get_enemy_observed_range(enemy, obj, myAgent))
    observe_range = np.arange(n_hexes)[observe_range_bool]
    if not observe_range:
        return None
    # === 根据机动距离筛除不可达范围 ===
    candidate_locs_bool = np.logical_and(cost_matrix[obj_loc_ind] <= time_threshold, observe_range_bool)
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    if not candidate_locs:
        return None
    n_candidates = candidate_locs.size
    # 1. 计算临近性
    move_costs = cost_matrix[obj_loc_ind, candidate_locs.reshape(1, -1)]
    max_cost, min_cost = np.max(move_costs), np.min(move_costs)
    if max_cost - min_cost < 1e-8:
        proximities = np.ones(n_candidates, dtype=float)
    else:
        proximities = (max_cost - move_costs) / max_cost
    # 2. 计算安全性
    if obj['sub_type'] == BopType.CruiseMissile:
        safty_norm = np.ones(n_candidates, dtype=float)
    else:
        if obj_type == BopType.Vehicle:
            enemy_threats = myAgent.situations.enemy_threat[0]
        elif obj_type == BopType.Infantry:
            enemy_threats = myAgent.situations.enemy_threat[1]
        elif obj['sub_type'] == BopType.Helicopter:
            enemy_threats = myAgent.situations.enemy_threat[2]
        else:
            enemy_threats = myAgent.situations.enemy_threat[3]
        candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
        max_threat, min_threat = np.max(candidate_threats), np.min(candidate_threats)
        if max_threat - min_threat < 1e-8:
            safty_norm = np.ones(n_candidates, dtype=float)
        else:
            safty_norm = (max_threat - candidate_threats) / max_threat
        # 综合评估，选点位
        total_measures = 0.7 * safty_norm + 0.3 * proximities
        best_ind = np.argmax(total_measures)
        best_loc_ind = candidate_locs[best_ind]
        best_loc = matind_2_mapid(best_loc_ind, map_size)
        return best_loc
